WPF MVVM应用程序中的键盘事件? | 您所在的位置:网站首页 › wpf 事件绑定 mvvm › WPF MVVM应用程序中的键盘事件? |
如何在不使用代码隐藏的情况下处理Keyboard.KeyDown事件? 我们正在尝试使用MVVM模式,并避免在代码隐藏文件中编写事件处理程序。 相关讨论 为了节省搜索者阅读响应的时间,从4/2011开始的快速答案是,如果没有代码,这是不可能的。 原因是-KeyDown是EventTrigger,但DataTriggers仅支持条件。 这可以通过一个简单的转换器轻松解决,但是当前只有XAML无法实现,除非您创建一个设置DataTrigger的EventTrigger-但是,如果这样做,则对代码的可读性说再见。 这里的主要答案仅适用于单键捕获(例如" Enter"键)。 一个更完整和最新的答案在这里:stackoverflow.com/a/38433681/93394。 (使用Windows Interactivity库中的EventTriggers来响应任何KeyDown事件。)为了提供更新的答案,.net 4.0框架允许您将KeyBinding Command绑定到视图模型中的命令,从而使您可以很好地做到这一点。 所以...如果您想听Enter键,则可以执行以下操作: 12345678 相关讨论 从.net 4开始,这绝对是正确的方法。需要更多支持 顺便说一句,这也适用于鼠标单击。示例: 另一个注意事项:这不适用于TextBlock,因为TextBlock不能聚焦。 InputBinding需要在层次结构中较高的对象上设置。 因此,正如OP最初询问的那样,您如何将其链接到输入的任何文本?就是OnKeyDown事件? 这会消耗输入内容,是否仍然会发出仅响应按键预览的命令,以便仍会发生与该按键有关的正常功能?例如,如果我想在某个控件上按下Tab键时运行命令,但我仍然希望Tab的默认行为能够正常运行(将焦点按Tab顺序移至下一个控件)。在按Tab键时,对Tab键使用此键绑定不再移动焦点。哇-有大约一千个答案,在这里我要添加另一个答案。 "为什么不实现这种前额拍击"的方式中最明显的事情是,后面的代码和ViewModel坐在同一房间里,可以这么说。没有理由不让他们进行对话。 如果您考虑一下,那么XAML已经与ViewModel的API紧密耦合,因此您最好还是从后面的代码中对其进行依赖。 遵循或忽略的其他明显规则仍然适用(接口,空检查 我总是在这样的代码背后创建一个属性: 1private ViewModelClass ViewModel { get { return DataContext as ViewModelClass; } }这是客户端代码。空检查用于帮助控制混合主机。 123456void someEventHandler(object sender, KeyDownEventArgs e) { if (ViewModel == null) return; /* ... */ ViewModel.HandleKeyDown(e); }按需要在背后的代码中处理事件(UI事件以UI为中心,所以可以),然后在ViewModelClass上有一个可以响应该事件的方法。担忧仍然分开。 1234ViewModelClass { public void HandleKeyDown(KeyEventArgs e) { /* ... */ } }所有其他这些附加属性和伏都教非常酷,这些技术对于其他一些事情确实很有用,但是在这里您可能会遇到一些更简单的事情…… 相关讨论 始终获取ViewModel的本地副本,而不是每次都调用该属性... var vm = ViewModel;,从那里使用vm代替ViewModel。有点晚了,但是这里。 微软的WPF团队最近发布了他们的WPF MVVM Toolkit的早期版本 。在其中,您将找到一个名为CommandReference的类,该类可以处理诸如键盘绑定之类的事情。查看他们的WPF MVVM模板以了解其工作原理。 相关讨论 我昨天刚刚尝试过,我认为这是绑定密钥的最干净的方法。不幸的是,无法同时绑定所有键(或者我无法弄清楚如何绑定),而且我需要整个键盘,因此您必须为每个键创建一个绑定,然后在按下shift键时再次创建所有键,然后再按一次Ctrl ...会很长。我们选择在代码中仅包含一个KeyUp处理程序,以在VM中调用方法。喜欢5行代码,而不是所有这些绑定。而且VM仍然不知道View。感谢您的回应 2020更新。 WPF已死,并且以上链接中的帖子已存档并放置在此处archive.codeplex.com/?p=wpf我通过使用具有3个依赖项属性的附加行为来完成此操作;一个是要执行的命令,一个是要传递给命令的参数,另一个是将导致命令执行的键。这是代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156public static class CreateKeyDownCommandBinding { /// /// Command to execute. /// public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(CommandModelBase), typeof(CreateKeyDownCommandBinding), new PropertyMetadata(new PropertyChangedCallback(OnCommandInvalidated))); /// /// Parameter to be passed to the command. /// public static readonly DependencyProperty ParameterProperty = DependencyProperty.RegisterAttached("Parameter", typeof(object), typeof(CreateKeyDownCommandBinding), new PropertyMetadata(new PropertyChangedCallback(OnParameterInvalidated))); /// /// The key to be used as a trigger to execute the command. /// public static readonly DependencyProperty KeyProperty = DependencyProperty.RegisterAttached("Key", typeof(Key), typeof(CreateKeyDownCommandBinding)); /// /// Get the command to execute. /// /// /// public static CommandModelBase GetCommand(DependencyObject sender) { return (CommandModelBase)sender.GetValue(CommandProperty); } /// /// Set the command to execute. /// /// /// public static void SetCommand(DependencyObject sender, CommandModelBase command) { sender.SetValue(CommandProperty, command); } /// /// Get the parameter to pass to the command. /// /// /// public static object GetParameter(DependencyObject sender) { return sender.GetValue(ParameterProperty); } /// /// Set the parameter to pass to the command. /// /// /// public static void SetParameter(DependencyObject sender, object parameter) { sender.SetValue(ParameterProperty, parameter); } /// /// Get the key to trigger the command. /// /// /// public static Key GetKey(DependencyObject sender) { return (Key)sender.GetValue(KeyProperty); } /// /// Set the key which triggers the command. /// /// /// public static void SetKey(DependencyObject sender, Key key) { sender.SetValue(KeyProperty, key); } /// /// When the command property is being set attach a listener for the /// key down event. When the command is being unset (when the /// UIElement is unloaded for instance) remove the listener. /// /// /// static void OnCommandInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { UIElement element = (UIElement)dependencyObject; if (e.OldValue == null && e.NewValue != null) { element.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown), true); } if (e.OldValue != null && e.NewValue == null) { element.RemoveHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnKeyDown)); } } /// /// When the parameter property is set update the command binding to /// include it. /// /// /// static void OnParameterInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { UIElement element = (UIElement)dependencyObject; element.CommandBindings.Clear(); // Setup the binding CommandModelBase commandModel = e.NewValue as CommandModelBase; if (commandModel != null) { element.CommandBindings.Add(new CommandBinding(commandModel.Command, commandModel.OnExecute, commandModel.OnQueryEnabled)); } } /// /// When the trigger key is pressed on the element, check whether /// the command should execute and then execute it. /// /// /// static void OnKeyDown(object sender, KeyEventArgs e) { UIElement element = sender as UIElement; Key triggerKey = (Key)element.GetValue(KeyProperty); if (e.Key != triggerKey) { return; } CommandModelBase cmdModel = (CommandModelBase)element.GetValue(CommandProperty); object parameter = element.GetValue(ParameterProperty); if (cmdModel.CanExecute(parameter)) { cmdModel.Execute(parameter); } e.Handled = true; } }要从xaml使用此功能,您可以执行以下操作: 123 Enter编辑:CommandModelBase是我用于所有命令的基类。它基于Dan Crevier的MVVM文章(此处)中的CommandModel类。这是我与CreateKeyDownCommandBinding一起使用的经过稍微修改的版本的来源: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354public abstract class CommandModelBase : ICommand { RoutedCommand routedCommand_; /// /// Expose a command that can be bound to from XAML. /// public RoutedCommand Command { get { return routedCommand_; } } /// /// Initialise the command. /// public CommandModelBase() { routedCommand_ = new RoutedCommand(); } /// /// Default implementation always allows the command to execute. /// /// /// public void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = CanExecute(e.Parameter); e.Handled = true; } /// /// Subclasses must provide the execution logic. /// /// /// public void OnExecute(object sender, ExecutedRoutedEventArgs e) { Execute(e.Parameter); } #region ICommand Members public virtual bool CanExecute(object parameter) { return true; } public event EventHandler CanExecuteChanged; public abstract void Execute(object parameter); #endregion }欢迎提出改进意见和建议。 相关讨论 谢谢保罗,我将尝试一下。 之前已经有人问过这个问题,但是CommandModelBase是从哪里来的呢?我想我错过了一些东西... 香港专业教育学院更新了答案,以包括CommandModelBase的描述。我本来应该把它包括在内。希望能帮助到你!几个月前,我研究了这个问题,并编写了一个标记扩展来解决问题。它可以像常规绑定一样使用: 123此扩展程序的完整源代码可以在这里找到: [WPF] Using InputBindings with the MVVM pattern
请注意,此解决方法可能不是很"干净",因为它通过反射使用了一些私有类和字段... 简短的答案是,如果没有代码隐藏,您将无法处理直接的键盘输入事件,但是您可以使用MVVM处理InputBindings(如果您需要的话,我可以向您展示相关示例)。 您能否提供有关您要在处理程序中执行的操作的更多信息? MVVM并不能完全避免代码落后。它仅用于严格与UI相关的任务。一个重要的例子是具有某种类型的"数据输入表单",在加载时,需要将焦点设置到第一个输入元素(文本框,组合框等)上。您通常会为该元素分配一个x:Name属性,然后将Window / Page / UserControl的" Loaded"事件挂接起来以将焦点设置到该元素。通过该模式这完全可以实现,因为该任务是以UI为中心的,并且与它表示的数据无关。 与karlipoppins答案类似,但是我发现如果没有以下添加/更改,它将无法正常工作: 12345我知道这个问题已经很老了,但是我之所以这样做,是因为这种类型的功能只是在Silverlight(5)中更易于实现。 所以也许其他人也会来这里。 在找不到所需的内容之后,我写了这个简单的解决方案。 原来这很简单。 它应该在Silverlight 5和WPF中都可以工作。 1234567891011121314151617181920212223242526272829303132333435363738public class KeyToCommandExtension : IMarkupExtension { public string Command { get; set; } public Key Key { get; set; } private void KeyEvent(object sender, KeyEventArgs e) { if (Key != Key.None && e.Key != Key) return; var target = (FrameworkElement)sender; if (target.DataContext == null) return; var property = target.DataContext.GetType().GetProperty(Command, BindingFlags.Public | BindingFlags.Instance, null, typeof(ICommand), new Type[0], null); if (property == null) return; var command = (ICommand)property.GetValue(target.DataContext, null); if (command != null && command.CanExecute(Key)) command.Execute(Key); } public Delegate ProvideValue(IServiceProvider serviceProvider) { if (string.IsNullOrEmpty(Command)) throw new InvalidOperationException("Command not set"); var targetProvider = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); if (!(targetProvider.TargetObject is FrameworkElement)) throw new InvalidOperationException("Target object must be FrameworkElement"); if (!(targetProvider.TargetProperty is EventInfo)) throw new InvalidOperationException("Target property must be event"); return Delegate.CreateDelegate(typeof(KeyEventHandler), this,"KeyEvent"); }用法: 1请注意,Command是字符串,而不是可绑定的ICommand。 我知道这不那么灵活,但是使用起来更干净,而且您有99%的时间需要。 虽然更改应该不是问题。 |
CopyRight 2018-2019 实验室设备网 版权所有 |